from pyglet.gl import *

from _global import flags, enum
from vector import Vector3
from quaternion import Quaternion
from primitives import Cube, Sphere, Primitive
from collisions import AABB, BoundSphere, BoundOBB

class Drawable(object):
	"""Drawable interface"""
	Traits = flags(GL = 1, MOVE=2, FRAM=4, TRAN=8, COLL=16, PHYS=32)
	CollisionFlags = flags(X_POS=1, X_NEG=2, X=3, Y_POS=4, Y_NEG=8, Y=12, Z_POS=16, Z_NEG=32, Z=48, INTERP=64, ALL=63)
	Bounds = enum("X", "Y", "Z")
	CollCheckType = enum("AUTO", "MANUAL")
	
	def __init__(self, traits=0):
		super(Drawable, self).__init__()
		self.origin = Vector3(0, 0, 0)
		self.moveTo = Vector3()
		
		#Traits for automation, all optional
		#GL - OpenGL details for drawing
		if traits & Drawable.Traits.GL: self.gl = TraitGL(self)
		else: self.gl = None
		#MOVE - controls movement/acceleration
		if traits & Drawable.Traits.MOVE: self.move = TraitMove()
		else: self.move = None
		#FRAM - frame control for animation
		if traits & Drawable.Traits.FRAM: self.fram = TraitFrame()
		else: self.fram = None
		#TRAN - transitions for position/rotation
		if traits & Drawable.Traits.TRAN: self.tran = TraitTransition()
		else: self.tran = None
		#COLL - collision detection properties
		if traits & Drawable.Traits.COLL: self.coll = TraitCollision(self)
		else: self.coll = None
		#PHYS - physics info for automated collision reaction
		if traits & Drawable.Traits.PHYS: self.phys = TraitPhysics(self)
		else: self.phys = None
	def load(self, game):
		self.game = game
		if self.gl and self.gl.id == 0:
			self.gl.id = glGenLists(1)
	def draw(self):
		if self.gl:
			glPushMatrix()
			glTranslatef(self.origin.x, self.origin.y, self.origin.z)
			if self.move and self.move.drawRotation:
				#This is the rotation order for quaternion results
				glRotatef(self.move.rotation.y, 0., 1., 0.)	#phi
				glRotatef(self.move.rotation.x, 1., 0., 0.)	#theta
				glRotatef(self.move.rotation.z, 0., 0., 1.)	#psi
			if self.gl.translate:
				glTranslatef(self.gl.translate.x, self.gl.translate.y, self.gl.translate.z)
			if self.gl.scale:
				glScalef(self.gl.scale.x, self.gl.scale.y, self.gl.scale.z)
			if self.gl.rotation:
				glRotatef(self.gl.rotation.y, 0., 1., 0.)
				glRotatef(self.gl.rotation.x, 1., 0., 0.)
				glRotatef(self.gl.rotation.z, 0., 0., 1.)
			if self.gl.paint == Primitive.Paint.TEXTURE:
				glEnable(self.gl.texture[0].target)
				glBindTexture(self.gl.texture[0].target, self.gl.texture[0].id)
			glCallList(self.gl.id)
			if self.gl.paint == Primitive.Paint.TEXTURE:
				glDisable(self.gl.texture[0].target)
			glPopMatrix()
			glPushMatrix()
			if self.game.debugMode:
				self._drawAABB()
			glPopMatrix()
	def step(self):
		if self.move:		#MOVE
			self.move.velocity.add(self.move.acceleration)
			v = self.move.velocity.copy()
			if not self.move.rotation == (0, 0, 0):
				a = self.move.acceleration.copy()
				a.rotate(self.move.rotation)
				v.rotate(self.move.rotation)
			self.moveTo = self.origin + v
		elif self.tran:		#TRANSITION
			pass
		if self.coll:		#COLLISIONS
			if self.coll.check == Drawable.CollCheckType.AUTO and not self.coll.fixed:
				flags = 0
				if self.move:
					if self.move.velocity.x > 0:
						flags |= Drawable.CollisionFlags.X_POS
					elif self.move.velocity.x < 0:
						flags |= Drawable.CollisionFlags.X_NEG
					if self.move.velocity.y > 0:
						flags |= Drawable.CollisionFlags.Y_POS
					elif self.move.velocity.y < 0:
						flags |= Drawable.CollisionFlags.Y_NEG
					if self.move.velocity.z > 0:
						flags |= Drawable.CollisionFlags.Z_POS
					elif self.move.velocity.z < 0:
						flags |= Drawable.CollisionFlags.Z_NEG
				colls = self.game.scene.checkCollisions(self, flags)
				for c in colls:
					self.collide(c[0], c[1])
					if c[0].coll and not c[0].coll.fixed:
						c[0].collide(self, Drawable._oppositeFlag(c[1]))
		if self.phys:		#PHYSICS
			pass
		if self.fram:		#FRAMES
			pass
		if self.move:		#MOVE again
			#finish relocating
			self.origin = self.moveTo
	def delete(self):
		if self in self.game.scene.drawables:
			self.game.scene.drawables.remove(self)
	def collide(self, drawable, side):
		"""Callback function for collision info
			Args:
				drawable:	object with which this drawable collided
				side:		AABB side of self that was hit
		"""
		pass
	@staticmethod
	def _oppositeFlag(flag):
		"""return opposite collision flag for {+|-}{x|y|z}"""
		if flag & 21:		#21 = X_POS | Y_POS | Z_POS
			return flag * 2
		elif flag & 42:		#42 = X_NEG | Y_NEG | Z_NEG
			return flag / 2
	
	def _drawAABB(self):
		if self.coll:
			b = self.coll.box
			glBegin(GL_LINE_STRIP)	#Top square
			glVertex3f(b.left, b.top, b.back)
			glVertex3f(b.right, b.top, b.back)
			glVertex3f(b.right, b.top, b.front)
			glVertex3f(b.left, b.top, b.front)
			glVertex3f(b.left, b.top, b.back)
			glEnd()
			glBegin(GL_LINE_STRIP)	#Bottom square
			glVertex3f(b.left, b.bottom, b.back)
			glVertex3f(b.right, b.bottom, b.back)
			glVertex3f(b.right, b.bottom, b.front)
			glVertex3f(b.left, b.bottom, b.front)
			glVertex3f(b.left, b.bottom, b.back)
			glEnd()
			glBegin(GL_LINES)
			glVertex3f(b.left, b.top, b.back)
			glVertex3f(b.left, b.bottom, b.back)
			glVertex3f(b.right, b.top, b.back)
			glVertex3f(b.right, b.bottom, b.back)
			glVertex3f(b.right, b.top, b.front)
			glVertex3f(b.right, b.bottom, b.front)
			glVertex3f(b.left, b.top, b.front)
			glVertex3f(b.left, b.bottom, b.front)
			glVertex3f(b.left, b.top, b.back)
			glVertex3f(b.left, b.bottom, b.back)
			glEnd()

class TraitGL(object):
	def __init__(self, drawable):
		self.parent = drawable
		self.id = 0		#OpenGL Display List ID
		self.scale = None
		self.translate = None
		self.rotation = None		#Extra rotation regardless of MOVE trait rotation
		self.paint = 0
		self.texture = []
	
	def scaleV(self, v):
		self.scale = v
		if self.parent.coll:
			self.parent.coll.box.scaleV(v)
	
	def createCube(self, type=0, args=[]):
		self.paint = type
		self.texture = args
		c = Cube(type)
		c.build(self.parent, args)
	
	def createSphere(self, type=0, args=[]):
		c = Sphere(type)
		c.build(self.parent, args)

class TraitMove(object):
	def __init__(self):
		self.speed = 0.
		self.velocity = Vector3()	#Linear velocity
		self.acceleration = Vector3()	#Linear acceleration
		self.rotation = Vector3()	#Rotation vector to orient vel and acc if desired, default (0,0,0)
		self.quat = Quaternion()
		self.drawRotation = False	#Rotate drawing using move rotation
	
	def updateRot(self):
		self.rotation = self.quat.getEulerDeg()
		self.rotation += (360., 360., 360.)
		self.rotation %= 360.

class TraitTransition(object):
	pass

class TraitCollision(object):
	def __init__(self, drawable):
		self.parent = drawable
		self.customFlags = 0		#User defined exclusion flags
		self.fixed = False			#Fixed objects don't need to react to collisions
		self.box = AABB(drawable)	#Axis-aligned bounding box, all collideables have one
		self.bound = None			#Optional sub volume for more detailed checking
		self.check = Drawable.CollCheckType.AUTO	#Default to automatic checking

class TraitPhysics(object):
	def __init__(self):
		self.mass = 1				#Mass of the object w/ center of gravity at origin
		#self.massDist = Vector3()	#TODO: Mass distribution vector, default (0,0,0)
		self.accum = None			#Force accumulator, reset to Vector3(0,0,0) each step
		self.struck = None			#List of drawables that hit into parent before it was parent's turn to check
	
	def clear(self):
		"""Clear the force accumulator"""
		self.accum = Vector()
		self.struck = []

class TraitFrame(object):
	pass
